#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "err.h"
#include "amf.h"
#include "libamf.h"

struct context {
	struct amf_ioctx *in;
	int indentation;
};

static void _print_utf8(struct context *c, int hdr)
{
	uint32_t length, i;
	unsigned char byte;
	static const char esc[256] = {
		['"'] = '"', ['\\'] = '\\', ['\a'] = 'a',
		['\b'] = 'b', ['\f'] = 'f', ['\n'] = 'n',
		['\r'] = 'r', ['\t'] = 't', ['\v'] = 'v',
	};

	if (hdr == 2)
		length = getu16(c->in);
	else
		length = getu32(c->in);

	for (i = 0; i < length; i++) {
		byte = getu8(c->in);

		if (!esc[byte] && byte >= 0x20)
			putchar(byte);
		else if (esc[byte])
			printf("\\%c", esc[byte]);
		else
			printf("\\%o", byte);
	}
}
static inline void print_utf8_long(struct context *c)
{
	_print_utf8(c, 4);
}
static inline void print_utf8(struct context *c)
{
	_print_utf8(c, 2);
}
static void print_number(struct context *c)
{
	char buf[512];
	char *s, *dot;

	snprintf(buf, 512, "%.3f", getf64(c->in));

	/* remove .000 */
	s = buf + strlen(buf) - 1;
	dot = strchr(buf, '.');
	if (dot) {
		while (s > dot && (*s == '0'))
			s--;
		if (*s == '.')
			s--;
		s[1] = 0;
	}
	fputs(buf, stdout);
}
static void indent(unsigned n)
{
	while (n--)
		fputs("  ", stdout);
}
static void print_value(struct context *c);
static void print_object(struct context *c, char type)
{
	bool first;

	if (type == AMF_TYPED_OBJECT) {
		print_utf8(c);
		putchar(' ');
	} else if (type == AMF_ECMA_ARRAY) {
		seek(c->in, 4);
	}

	first = true;
	fputs("{\n", stdout);
	c->indentation++;
	while (peeku24(c->in) != AMF_OBJECT_END) {
		if (!first)
			fputs(",\n", stdout);
		indent(c->indentation);

		print_utf8(c);
		fputs(": ", stdout);
		print_value(c);

		first = false;
	}
	c->indentation--;
	putchar('\n');
	indent(c->indentation);
	putchar('}');

}
static void print_strict_array(struct context *c)
{
	uint32_t len, i;

	len = getu32(c->in);

	putchar('[');
	for (i = 0; i < len; i++) {
		if (i)
			fputs(", ", stdout);

		print_value(c);
	}
	putchar(']');
}

static void print_value(struct context *c)
{
	char type;

	type = getu8(c->in);

	switch (type) {
	case AMF_NULL:
		fputs("null", stdout);
		break;
	case AMF_UNDEFINED:
		fputs("undefined", stdout);
		break;
	case AMF_UNSUPPORTED:
		fputs("unsupported", stdout);
		break;
	case AMF_BOOLEAN:
		fputs(getu8(c->in) ? "true" : "false", stdout);
		break;
	case AMF_REFERENCE:
		printf("#%u", getu16(c->in));
		break;
	case AMF_DATE:
		printf("%.3f%+d", getf64(c->in), gets16(c->in));
		break;
	case AMF_NUMBER:
		print_number(c);
		break;
	case AMF_STRING:
		print_utf8(c);
		break;
	case AMF_LONG_STRING:
	case AMF_XML_DOCUMENT:
		print_utf8_long(c);
		break;
	case AMF_OBJECT:
	case AMF_ECMA_ARRAY:
	case AMF_TYPED_OBJECT:
		print_object(c, type);
		break;
	case AMF_STRICT_ARRAY:
		print_strict_array(c);
		break;
	default:
		longjmp(c->in->jmpenv, -2);
	}
}

int amf_print_scriptdata(const char *base, unsigned size)
{
	int r;
	struct context c[1];
	struct amf_ioctx io[1];

	c->in = io;
	c->in->base = base;
	c->in->pos = 0;
	c->in->end = size;
	c->indentation = 0;

	r = setjmp(io->jmpenv);
	switch (r) {
	case 0:
		print_value(c);
		fputs(", ", stdout);
		print_value(c);
		putchar('\n');
		break;
	case -1:
		pmsgmsg("amf", "EOF exception");
		break;
	case -2:
		pmsgmsg("amf", "Unknown AMF marker");
		break;
	default:
		break;
	}

	return r;
}
